
#include <QGraphicsScene>
#include <QGraphicsEllipseItem>
#include <QTextEdit>
#include <QLineEdit>
#include <QMouseEvent>

#include "HalmaBoard.h"
#include "HalmaPiece.h"
#include "client/ClientPlayer.h"

Qt::GlobalColor HalmaBoard::player_color[6] = {Qt::blue, Qt::red, Qt::yellow, Qt::green, Qt::magenta, Qt::gray};

HalmaBoard::HalmaBoard(QWidget *parent) :
    QGraphicsView(parent)
{
    setScene(new QGraphicsScene(this));
    setRenderHints(QPainter::Antialiasing);

    hole_size = 28;
    distance_x = hole_size * 0.8;
    distance_y = distance_x * 1.73;
    player_num = 0;
    current_piece = NULL;

    drawBoard();

    for(int i = 0; i < 6; i++){
        player_names[i] = scene()->addText(tr("Waiting for a new player..."));
        player_names[i]->hide();
    }
}

void HalmaBoard::drawBoard(int border_length){
    scene()->clear();
    hole_list.clear();
    board.clear();

    board.resize((border_length + 1) * border_length * 6 + 2);
    board.fill(0);
    this->border_length = border_length;

    int line_width = 0, line_y = 0;

    for(int i = 0; i < border_length; i++){
        for(int j = 0; j <= i; j++){
            QGraphicsEllipseItem *item = scene()->addEllipse(-hole_size / 2, -hole_size / 2, hole_size, hole_size);
            item->setPos(j * distance_x * 2 - line_width, line_y);
            hole_list.append(item);
        }

        line_width += distance_x;
        line_y += distance_y;
    }

    line_width = distance_x * border_length * 3;
    for(int i = 0; i < border_length; i++){
        int j_max = border_length * 3 - i;
        for(int j = 0; j <= j_max; j++){
            QGraphicsEllipseItem *item = scene()->addEllipse(-hole_size / 2, -hole_size / 2, hole_size, hole_size);
            item->setPos(j * distance_x * 2 - line_width, line_y);
            hole_list.append(item);
        }

        line_width -= distance_x;
        line_y += distance_y;
    }

    for(int i = 0; i <= border_length; i++){
        int j_max = border_length * 2 + i;
        for(int j = 0; j <= j_max; j++){
            QGraphicsEllipseItem *item = scene()->addEllipse(-hole_size / 2, -hole_size / 2, hole_size, hole_size);
            item->setPos(j * distance_x * 2 - line_width, line_y);
            hole_list.append(item);
        }

        line_width += distance_x;
        line_y += distance_y;
    }

    line_width = distance_x * (border_length - 1);
    for(int i = 0; i < border_length; i++){
        int j_max = border_length - i;
        for(int j = 0; j < j_max; j++){
            QGraphicsEllipseItem *item = scene()->addEllipse(-hole_size / 2, -hole_size / 2, hole_size, hole_size);
            item->setPos(j * distance_x * 2 - line_width, line_y);
            hole_list.append(item);
        }

        line_width -= distance_x;
        line_y += distance_y;
    }
}

void HalmaBoard::arrangePieces(int player_id){
    if(player_id < 1 || player_id > 6){
        return;
    }

    removePieces(player_id);

    player_id--;

    int i = 0, max = 0;
    switch(player_id){
    case 0:
        max = (border_length + 1) * border_length * 6;
        i = max - 9;
        break;
    case 1:
        i = 0;
        max = 9;
        break;
    //待添加其他4个位置
    }

    while(i <= max){
        HalmaPiece *piece = new HalmaPiece(player_color[player_id]);
        scene()->addItem(piece);
        piece->setPos(hole_list.at(i)->pos());
        /*if(player_id == Self->getPlayerId() - 1){
            piece->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
        }*/
        player_piece[player_id].append(piece);
        board[i] = player_id + 1;
        i++;
    }
}

void HalmaBoard::removePieces(int player_id){
    if(player_id >= 1 && player_id <= 6){
        player_id--;

        foreach(HalmaPiece *piece, player_piece[player_id]){
            QPoint d = discretePos(piece->pos());
            board[holeId(d.x(), d.y())] = 0;
            scene()->removeItem(piece);
        }
        player_piece[player_id].clear();
    }
}

void HalmaBoard::enablePieces(){
    foreach(HalmaPiece *piece, player_piece[Self->getPlayerId() - 1]){
        piece->setFlag(QGraphicsItem::ItemIsMovable, true);
        piece->setFlag(QGraphicsItem::ItemIsSelectable, true);
    }
}

void HalmaBoard::disablePieces(){
    foreach(HalmaPiece *piece, player_piece[Self->getPlayerId() - 1]){
        piece->setFlag(QGraphicsItem::ItemIsMovable, false);
        piece->setFlag(QGraphicsItem::ItemIsSelectable, false);
    }
}

void HalmaBoard::movePiece(int player_id, int from, int to){
    HalmaPiece *piece = player_piece[player_id - 1].at(from);
    from = holeId(piece->pos());
    piece->setPos(hole_list.at(to)->pos());

    board[to] = player_id;
    board[from] = 0;
}

void HalmaBoard::aiMovePiece(){
    double max_distance = 0.0;
    double max_tan = 1000000.0;
    int from = -1, to;
    HalmaPiece *piece;

    const QList<HalmaPiece *> &my_piece = player_piece[Self->getPlayerId() - 1];

    for(int i = 0; i < my_piece.length(); i++){
        piece = my_piece.at(i);
        int cur_to = aiMovePiece(piece);
        if(cur_to < 0){
            continue;
        }
        double del_x = hole_list.at(0)->pos().x() - hole_list.at(cur_to)->pos().x();
        double del_y = hole_list.at(0)->pos().y() - hole_list.at(cur_to)->pos().y();
        del_x = del_x >= 0 ? del_x : (0 - del_x);
        del_y = del_y >= 0 ? del_y : (0 - del_y);
        if(del_x / del_y < max_tan){
            max_tan = del_x / del_y;
            from = i;
            to = cur_to;
        }

        //double distance = DistanceSquare(piece->pos(), hole_list.at(cur_to)->pos());
        /*double distance = DistanceSquare(hole_list.at(0)->pos(), hole_list.at(cur_to)->pos());
        if(distance > max_distance){
            max_distance = distance;
            from = i;
            to = cur_to;
        }*/
    }

    if(from >= 0){
        Self->movePiece(from, to);
    }
}

int HalmaBoard::aiMovePiece(HalmaPiece *piece){
    double max_distance = 0.0;
    int sub_to = -1;
    for (int i = 0; i < hole_list.length(); ++i) {
        if(canMoveTo(piece->pos(), hole_list[i]->pos())){
            double distance = DistanceSquare(piece->pos(), hole_list[i]->pos());
            if (distance > max_distance){
                max_distance = distance;
                sub_to = i;
            }
        }
    }
    return sub_to;
}

double HalmaBoard::DistanceSquare(const QPointF &pos1, const QPointF &pos2){
    return (pos2.y() - pos1.y()) * ((pos1.x() - pos2.x()) * (pos1.x() - pos2.x()) + (pos1.y() - pos2.y()) * (pos1.y() - pos2.y()));
}

void HalmaBoard::roundStart(){
    Self->setProperty("phase", "playing");
    Self->notifyProperty("phase");

    is_first_step = true;
    if(Self->isHosted()){
        aiMovePiece();
    }else{
        enablePieces();
    }

    emit roundChanged(true);

    if(Self->isHosted()){
        roundEnd();
    }
}

void HalmaBoard::roundEnd(){
    if(isWinner()){
        Self->notify(GameOver);
    }else{
        Self->setProperty("phase", "end");
        Self->notifyProperty("phase");
    }

    disablePieces();
    emit roundChanged(false);
}

int HalmaBoard::holeId(int x, int y) const{
    if((x & 0x1) != (y & 0x1)){
        return -1;
    }

    if(y < border_length){
        if(x < -y || x > y){
            return -1;
        }
        return y * (y + 1) / 2 + (x + y) / 2;

    }else if(y <= border_length * 2){
        int max_x = (border_length << 2) - y;
        if(x < -max_x || x > max_x){
            return -1;
        }

        int offset = ((border_length + 1) * border_length + x + max_x) >> 1;

        max_x++;
        int top_line = border_length * 3 + 1;
        while(max_x < top_line){
            max_x++;
            offset += max_x;
        }
        return offset;

    }else if(y <= border_length * 3){
        if(x < -y || x > y){
            return -1;
        }

        int offset = (border_length + 1) * border_length * 3 + border_length + 1 + (x + y) / 2;

        int bottom_line = border_length * 2 + 2;
        while(y >= bottom_line){
            offset += y;
            y--;
        }
        return offset;

    }else if(y <= border_length * 4){
        int max_x = border_length - (y - border_length * 3);
        if(x < -max_x || x > max_x){
            return -1;
        }

        int offset = (border_length + 1) * border_length * 3 + 1 + (border_length + 1) * border_length / 2 * 5 + (x + max_x) / 2;

        max_x += 2;
        while(max_x <= border_length){
            offset += max_x;
            max_x++;
        }

        return offset;
    }

    return -1;
}

int HalmaBoard::holeId(QPoint pos) const{
    return holeId(pos.x(), pos.y());
}

int HalmaBoard::holeId(QPointF pos) const{
    return holeId(discretePos(pos));
}

QPoint HalmaBoard::holePos(int x, int y) const{
    int hole_id = holeId(x, y);

    if(hole_id < 0 || hole_id >= hole_list.length()){
        return QPoint();
    }

    return hole_list.at(hole_id)->pos().toPoint();
}

bool HalmaBoard::isValidPoint(const QPoint &p) const{
    if((p.x() & 0x1) != (p.y() & 0x1)){
        return false;
    }

    if(p.y() < border_length){
        return -p.y() <= p.x() && p.x() <= p.y();

    }else if(p.y() <= border_length * 2){
        int max_x = (border_length << 2) - p.y();
        return p.x() >= -max_x && p.x() <= max_x;

    }else if(p.y() <= border_length * 3){
        return p.x() >= -p.y() && p.x() <= p.y();

    }else if(p.y() <= border_length * 4){
        int max_x = border_length - (p.y() - border_length * 3);
        return p.x() >= -max_x && p.x() <= max_x;
    }

    return false;
}

bool HalmaBoard::canMoveTo(const QPoint &from, const QPoint &to) const{
    QPoint d = to - from;

    //障碍规则
    if(board[holeId(to)] != 0){
        return false;
    }

    //移动规则
    if(isNeighbor(from, to)){
        return true;
    }

    //方向规则
    if(d.x() != 0 && d.y() != 0 && qAbs(d.y() / d.x()) != 1){
        return false;
    }
    if(d.x() == 0 && d.y() != 0){
        return false;
    }

    //跳跃规则
    QPoint delta(0, 0);
    if(d.y() == 0){
        delta.setX(d.x() == 0 ? 0 : (d.x() > 0 ? 2 : -2));
    }else{
        delta.setX(d.x() == 0 ? 0 : (d.x() > 0 ? 1 : -1));
        delta.setY(d.y() > 0 ? 1 : -1);
    }

    int step = 0;
    QPoint curpos = from + delta;
    while(curpos != to){
        if(board[holeId(curpos)] == 0){
            return false;
        }
        step++;
        curpos += delta;
    }

    if(!Config.value("game/ruleOdd").toBool() && step % 2 == 1){
        return false;
    }

    if(!Config.value("game/ruleEven").toBool() && step % 2 == 0){
        return false;
    }

    return true;
}

bool HalmaBoard::canMoveTo(const QPointF &from, const QPointF &to) const{
    return canMoveTo(discretePos(from), discretePos(to));
}

bool HalmaBoard::isNeighbor(const QPoint &p1, const QPoint &p2){
    if(p1.y() == p2.y()){
        return qAbs(p1.x() - p2.x()) == 2;
    }else if(qAbs(p1.y() - p2.y()) == 1){
        return qAbs(p1.x() - p2.x()) == 1;
    }

    return false;
}

bool HalmaBoard::isNeighbor(const QPointF &p1, const QPointF &p2) const{
    return isNeighbor(discretePos(p1), discretePos(p2));
}

void HalmaBoard::setPlayerNum(int player_num){
    this->player_num = player_num;

    int i;
    for(i = 0; i < player_num; i++){
        player_names[i]->show();
    }
    for(; i < 6; i++){
        player_names[i]->hide();
    }

    player_names[0]->setPos(distance_x, distance_y * border_length * 4);

    switch(player_num){
    case 2:
        player_names[1]->setPos(-distance_x - player_names[1]->boundingRect().width(), -player_names[1]->boundingRect().height());
        if(Self->getPlayerId() == 2){
            rotate(180);
            for(int i = 0; i < 2; i++){
                player_names[i]->setRotation(-180);
                qreal width = player_names[i]->document()->size().width();
                qreal height = player_names[i]->document()->size().height();
                player_names[i]->moveBy(width, height);
            }
        }
        break;
    case 3:
        player_names[1]->setPos(-distance_x * border_length, distance_y * border_length);
        player_names[2]->setPos(distance_x * border_length, distance_y * border_length);
        break;
    case 4:
        break;
    case 5:
        break;
    case 6:
        break;
    }
}

void HalmaBoard::addPlayer(int player_id, const QString &name){
    if(player_id >= 1 && player_id <= 6){
        arrangePieces(player_id);

        player_id--;
        player_names[player_id]->setPlainText(name);
    }

    setPlayerNum(player_num + 1);
}

void HalmaBoard::setPlayerProperty(int player_id, const QString &property){
    int i = property.indexOf(' ');
    QString key = property.left(i);
    QString value = property.mid(i + 1);

    if(key == "player_name"){
        player_names[player_id - 1]->setPlainText(value);
    }else if(key == "phase" && player_id == Self->getPlayerId()){
        if(value == "start"){
            roundStart();
        }
    }
}

void HalmaBoard::mousePressEvent(QMouseEvent *event){
    QGraphicsView::mousePressEvent(event);

    if(!scene()->selectedItems().isEmpty()){
        current_piece = scene()->selectedItems().first();
        current_piece_from = current_piece->pos();

        disablePieces();
        current_piece->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
    }
}

void HalmaBoard::mouseReleaseEvent(QMouseEvent *event){
    QGraphicsView::mouseReleaseEvent(event);

    if(current_piece == NULL){
        return;
    }

    if(!scene()->selectedItems().isEmpty()){
        moveCurrentPieceTo(current_piece->pos());
    }else if(current_piece != NULL){
        moveCurrentPieceTo(mapToScene(event->pos()));
    }
}

void HalmaBoard::resizeEvent(QResizeEvent *){
    fitInView(sceneRect(), Qt::KeepAspectRatio);
}

QPoint HalmaBoard::discretePos(const QPointF &p) const{
    return QPoint(p.x() / distance_x, p.y() / distance_y);
}

void HalmaBoard::moveCurrentPieceTo(QPointF to){
    adjustPiecePos(to);

    if(canMoveTo(current_piece_from, to)){
        bool is_moving = false;
        if(is_first_step){
            is_first_step = false;
            is_moving = isNeighbor(current_piece_from, to);
        }else if(isNeighbor(current_piece_from, to)){
            return;
        }

        int to_i = holeId(to);
        current_piece_from = to;
        current_piece->setSelected(true);

        const QList<HalmaPiece *> &my_piece = player_piece[Self->getPlayerId() - 1];
        for(int i = 0; i < my_piece.length(); i++){
            if(my_piece.at(i) == current_piece){
                Self->movePiece(i, to_i);
                break;
            }
        }

        if(is_moving){
            roundEnd();
        }

    }else{
        current_piece->setPos(current_piece_from);
    }
}

void HalmaBoard::adjustPiecePos(QPointF &p) const{
    float min_distance = 100000000.0;
    QGraphicsEllipseItem *nearest = NULL;
    foreach(QGraphicsEllipseItem *hole, hole_list){
        QPointF d = hole->pos() - p;
        float distance = d.x() * d.x() + d.y() * d.y();
        if(distance < min_distance){
            min_distance = distance;
            nearest = hole;
        }
    }

    if(nearest){
        p = nearest->pos();
    }
}

void HalmaBoard::animateMovePieceTo(HalmaPiece *piece, const QPoint &to){

}

bool HalmaBoard::isWinner() const{
    int player_id = Self->getPlayerId();

    int i = 0, max = 0;
    switch(player_id){
    case 1:
        i = 0;
        max = 9;
        break;
    case 2:
        max = (border_length + 1) * border_length * 6;
        i = max - 9;
        break;
    default:
        return false;
    //待添加其他4个位置
    }

    while(i <= max){
        if(board[i] != player_id){
            return false;
        }
    }

    return true;
}
